﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="ColorGradient.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2007, 2008, 2009, 2010, 2011
//       Roland Rosenkranz (Glatzemann@email.de)
//
//    Based on libnoise by Jason Bevins
//      Copyright (C) 2003, 2004 Jason Bevins (licensed under LGPL)
// </copyright>
// <license>
//   This file is part of starLiGHT.Noise.
//
//   starLiGHT.Noise is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Lesser General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   starLiGHT.Noise is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Lesser General Public License for more details.
//
//   You should have received a copy of the GNU Lesser General Public License
//   along with starLiGHT.Noise. If not, see http://www.gnu.org/licenses/.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.Noise are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 10385                 $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2011-06-14 07:59:17 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.Noise.Noise
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    #endregion

    /// Defines a color gradient.
    ///
    /// A color gradient is a list of gradually-changing colors.  A color
    /// gradient is defined by a list of <i>gradient points</i>.  Each
    /// gradient point has a position and a color.  In a color gradient, the
    /// colors between two adjacent gradient points are linearly interpolated.
    ///
    /// To add a gradient point to the color gradient, pass its position and
    /// color to the AddGradientPoint() method.
    ///
    /// To retrieve a color from a specific position in the color gradient,
    /// pass that position to the GetColor() method.
    ///
    /// This class is a useful tool for coloring height maps based on
    /// elevation.
    ///
    /// <b>Gradient example</b>
    ///
    /// Suppose a gradient object contains the following gradient points:
    /// - -1.0 maps to black.
    /// - 0.0 maps to white.
    /// - 1.0 maps to red.
    ///
    /// If an application passes -0.5 to the GetColor() method, this method
    /// will return a gray color that is halfway between black and white.
    ///
    /// If an application passes 0.25 to the GetColor() method, this method
    /// will return a very light pink color that is one quarter of the way
    /// between white and red.
    class GradientColor
    {
        /// Number of gradient points.
        private int gradientPointCount;

        /// Array that stores the gradient points.
        private List<GradientPoint> gradientPoints;

        /// A color object that is used by a gradient object to store a
        /// temporary value.
        private Color workingColor;

        /// Constructor.
        public GradientColor()
        {
            this.gradientPoints = null;
        }

        /// Adds a gradient point to this gradient object.
        ///
        /// @param gradientPos The position of this gradient point.
        /// @param gradientColor The color of this gradient point.
        ///
        /// @pre No two gradient points have the same position.
        ///
        /// @throw noise::ExceptionInvalidParam See the precondition.
        ///
        /// It does not matter which order these gradient points are added.
        public void AddGradientPoint(double gradientPos, Color gradientColor)
        {
            int insertionPos = this.FindInsertionPos(gradientPos);
            this.InsertAtPos(insertionPos, gradientPos, gradientColor);
        }

        /// Deletes all the gradient points from this gradient object.
        ///
        /// @post All gradient points from this gradient object are deleted.
        public void Clear()
        {
            this.gradientPoints = null;
            this.gradientPointCount = 0;
        }

        /// Returns the color at the specified position in the color gradient.
        ///
        /// @param gradientPos The specified position.
        ///
        /// @returns The color at that position.
        public Color GetColor(double gradientPos)
        {
            ////assert (this.gradientPointCount >= 2);

            // Find the first element in the gradient point array that has a gradient
            // position larger than the gradient position passed to this method.
            int indexPos;
            for (indexPos = 0; indexPos < this.gradientPointCount; indexPos++)
            {
                if (gradientPos < this.gradientPoints[indexPos].pos)
                {
                    break;
                }
            }

            // Find the two nearest gradient points so that we can perform linear
            // interpolation on the color.
            int index0 = this.ClampValue(indexPos - 1, 0, this.gradientPoints.Count - 1);
            int index1 = this.ClampValue(indexPos, 0, this.gradientPoints.Count - 1);

            // If some gradient points are missing (which occurs if the gradient
            // position passed to this method is greater than the largest gradient
            // position or less than the smallest gradient position in the array), get
            // the corresponding gradient color of the nearest gradient point and exit
            // now.
            if (index0 == index1)
            {
                this.workingColor = this.gradientPoints[index1].color;
                return this.workingColor;
            }

            // Compute the alpha value used for linear interpolation.
            double input0 = this.gradientPoints[index0].pos;
            double input1 = this.gradientPoints[index1].pos;
            double alpha = (gradientPos - input0) / (input1 - input0);

            // Now perform the linear interpolation given the alpha value.
            Color color0 = this.gradientPoints[index0].color;
            Color color1 = this.gradientPoints[index1].color;
            this.workingColor = this.LinearInterpColor(color0, color1, (float)alpha);
            return this.workingColor;
        }

        /// Returns a pointer to the array of gradient points in this object.
        ///
        /// @returns A pointer to the array of gradient points.
        ///
        /// Before calling this method, call GetGradientPointCount() to
        /// determine the number of gradient points in this array.
        ///
        /// It is recommended that an application does not store this pointer
        /// for later use since the pointer to the array may change if the
        /// application calls another method of this object.
        public List<GradientPoint> GetGradientPointArray()
        {
            return this.gradientPoints;
        }

        /// Returns the number of gradient points stored in this object.
        ///
        /// @returns The number of gradient points stored in this object.
        public int GetGradientPointCount()
        {
            return this.gradientPoints.Count;
        }

        /// Determines the array index in which to insert the gradient point
        /// into the internal gradient-point array.
        ///
        /// @param gradientPos The position of this gradient point.
        ///
        /// @returns The array index in which to insert the gradient point.
        ///
        /// @pre No two gradient points have the same input value.
        ///
        /// @throw noise::ExceptionInvalidParam See the precondition.
        ///
        /// By inserting the gradient point at the returned array index, this
        /// object ensures that the gradient-point array is sorted by input
        /// value.  The code that maps a value to a color requires a sorted
        /// gradient-point array.
        private int FindInsertionPos(double gradientPos)
        {
            if (this.gradientPoints == null) this.gradientPoints = new List<GradientPoint>();

            int insertionPos;
            for (insertionPos = 0; insertionPos < this.gradientPoints.Count; insertionPos++)
            {
                if (gradientPos < this.gradientPoints[insertionPos].pos)
                {
                    // We found the array index in which to insert the new gradient point.
                    // Exit now.
                    break;
                }
                else if (gradientPos == this.gradientPoints[insertionPos].pos)
                {
                    // Each gradient point is required to contain a unique gradient
                    // position, so throw an exception.
                    throw new Exception("invalid params");
                }
            }
            return insertionPos;
        }

        /// Inserts the gradient point at the specified position in the
        /// internal gradient-point array.
        ///
        /// @param insertionPos The zero-based array position in which to
        /// insert the gradient point.
        /// @param gradientPos The position of this gradient point.
        /// @param gradientColor The color of this gradient point.
        ///
        /// To make room for this new gradient point, this method reallocates
        /// the gradient-point array and shifts all gradient points occurring
        /// after the insertion position up by one.
        ///
        /// Because this object requires that all gradient points in the array
        /// must be sorted by the position, the new gradient point should be
        /// inserted at the position in which the order is still preserved.
        private void InsertAtPos(int insertionPos, double gradientPos, Color gradientColor)
        {
            if (this.gradientPoints == null) this.gradientPoints = new List<GradientPoint>();

            GradientPoint gp = new GradientPoint();
            gp.pos = gradientPos;
            gp.color = gradientColor;
            this.gradientPoints.Insert(insertionPos, gp);
            this.gradientPointCount++;
        }

        int ClampValue(int value, int begin, int end)
        {
            if (value < begin)
                value = begin;
            else if (value > end)
                value = end;

            return value;
        }

        // Performs linear interpolation between two colors and stores the result
        // in out.
        Color LinearInterpColor(Color color0, Color color1, float alpha)
        {
            ////////TODO: diese Funktion prüfen...

            Color outCol;

            float a = this.BlendChannel(color0.A, color1.A, alpha);
            float b = this.BlendChannel(color0.B, color1.B, alpha);
            float g = this.BlendChannel(color0.G, color1.G, alpha);
            float r = this.BlendChannel(color0.R, color1.R, alpha);

#if XNA4
            outCol = new Color(r, g, b) * a;
#else
            outCol = new Color((byte)r, (byte)g, (byte)b, (byte)a);
#endif

            return outCol;
        }

        // Performs linear interpolation between two 8-bit channel values.
        float BlendChannel(float channel0, float channel1, float alpha)
        {
            float c0 = channel0 / 255.0f;
            float c1 = channel1 / 255.0f;
            return ((c1 * alpha) + (c0 * (1.0f - alpha))) * 255.0f;
        }
    }
}

